#include "../include/MainWindow.h"
#include "../include/Functions.h"
#include <cmath>
#include <FTGL/ftgl.h>

MainWindow::MainWindow(int width, int height, std::string title)
	: sf::RenderWindow(sf::VideoMode(width, height), title, sf::Style::Default, sf::ContextSettings(24, 0, 0, 2, 0))
{
    glewInit();
    onCreate();
    FOV = 60;
    zNear = 0.1;
	zFar = 2500;
    yaw = 0;
    pitch = 0;
    camPos[0] = 270;
	camPos[1] = 50;
    camPos[2] = -270;
    terrain = NULL;
    skybox = NULL;
	water = NULL;
    clock = NULL;
    setMouseCursorVisible(false);
    setupViewport();
    flying = false;
    //g = new Grass("resources/images/grass_object.jpg", "resources/images/grass_object_alpha.jpg");
   // gc = new GrassCluster(290,30,-290,20,20,g);
}

MainWindow::~MainWindow() {
    delete terrain;
    delete skybox;
}

void MainWindow::render() {
    planeCollision();
    static GLfloat lightPos[4] = {1300, 500, -1200, 0};
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(pitch, 1, 0, 0);
    glRotatef(yaw, 0, 1, 0);
    skybox->render();
    glTranslatef(-camPos[0], -camPos[1], -camPos[2]);
	glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    vec3 cam;
    cam.x = camPos[0];
    cam.y = camPos[1];
    cam.z = camPos[2];
    terrain->render(cam);
	//This variable is used to pass the time elapsed in seconds since the start of the program
	water->timeCounter = water->timeCounter + clock->getElapsedTime().asSeconds();
    water->render(cam);
    if(flying) {
        lockOnPlane();
    }
    plane->update();
    plane->render(cam);
    renderInstanceList();
    //gc->render(cam);
}

void MainWindow::setClock(sf::Clock *clock) {
    this->clock = clock;
}

void MainWindow::adjustCamera(float x, float y, float z) {
    camPos[0] += x;
    camPos[1] += y;
    camPos[2] += z;
}

void MainWindow::setRotation(GLfloat yaw, GLfloat pitch) {
    this->yaw = fmod(yaw, 360);
    this->pitch = fmod(pitch, 180);
    if(this->pitch > 90) {
        this->pitch = 90;
    }
}

void MainWindow::adjustRotation(GLfloat yaw, GLfloat pitch) {
    this->yaw += yaw;
    this->pitch += pitch;
    this->yaw = fmod(this->yaw, 360);
    this->pitch = fmod(this->pitch, 180);
    if(this->pitch > 90) {
        this->pitch = 90;
    } else if(this->pitch < -90) {
        this->pitch = -90;
    }
}

void MainWindow::moveLeft(GLfloat speed) {
    float time;
    if(clock == NULL) {
        time = 1;
    } else {
        time = clock->getElapsedTime().asSeconds();
    }
    camPos[0] -= cos((yaw/180)*M_PI)*(time*speed);
    camPos[2] -= sin((yaw/180)*M_PI)*(time*speed);
}

void MainWindow::moveRight(GLfloat speed) {
    float time;
    if(clock == NULL) {
        time = 1;
    } else {
        time = clock->getElapsedTime().asSeconds();
    }
    camPos[0] += cos((yaw/180)*M_PI)*(time*speed);
    camPos[2] += sin((yaw/180)*M_PI)*(time*speed);
}

void MainWindow::moveForward(GLfloat speed) {
    float time;
    if(clock == NULL) {
        time = 1;
    } else {
        time = clock->getElapsedTime().asSeconds();
    }
    camPos[0] += sin((yaw/180)*M_PI)*(time*speed);
    camPos[1] -= sin((pitch/180)*M_PI)*(time*speed);
    camPos[2] -= cos((yaw/180)*M_PI)*(time*speed);
}

void MainWindow::moveBackward(GLfloat speed) {
    float time;
    if(clock == NULL) {
        time = 1;
    } else {
        time = clock->getElapsedTime().asSeconds();
    }
    camPos[0] -= sin((yaw/180)*M_PI)*(time*speed);
    camPos[1] += sin((pitch/180)*M_PI)*(time*speed);
    camPos[2] += cos((yaw/180)*M_PI)*(time*speed);
}

void MainWindow::setupShaders() {
    terrainShaderProg = CG::setupShader("resources/shaders/terrain.vert", "resources/shaders/terrain.frag");
    skyShaderProg = CG::setupShader("resources/shaders/skybox.vert", "resources/shaders/skybox.frag");
    waterShaderProg = CG::setupShader("resources/shaders/water.vert", "resources/shaders/water.frag");
    modelShaderProg = CG::setupShader("resources/shaders/model.vert", "resources/shaders/model.frag");
}

void MainWindow::setTerrain(std::string filename) {
    terrain = new Terrain(filename, terrainShaderProg);
	terrain->setScale(1, 100, 1);
}

void MainWindow::setSkybox(std::string filename) {
    skybox = new SkyBox(filename, skyShaderProg);
    skybox->setScale(zFar, zFar, zFar);
}

void MainWindow::setWater(std::string filename){
    water = new Water(filename, waterShaderProg);
	water->setScale(8,0.5,8);
    water->setPosition(-512, 2, 512);
}

void MainWindow::loadingScreen(std::string loadingText) {
    static bool init = true;
    static FTGLPixmapFont font("arial.ttf");
    if(init) {
        font.FaceSize(24);
    }
    FTPoint point((getSize().x-font.Advance(loadingText.c_str()))/2, (getSize().y-font.LineHeight())/2);

    glPixelTransferf(GL_RED_BIAS, -1);
    glPixelTransferf(GL_BLUE_BIAS, -1);
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0, getSize().x, 0, getSize().y, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    font.Render(loadingText.c_str(), -1, point);
    display();
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPixelTransferf(GL_RED_BIAS, 0);
    glPixelTransferf(GL_BLUE_BIAS, 0);
}

void MainWindow::printFPS() {
    if(clock == NULL) {
        return;
    }
    static bool init = true;
    std::string output = "FPS: " + CG::numberToString(1.0/clock->getElapsedTime().asSeconds(), 2);
    static FTGLPixmapFont font("arial.ttf");
    if(init) {
        font.FaceSize(24);
        init = false;
    }
    FTPoint point(5, getSize().y-(font.LineHeight()/2)-5);

    glActiveTexture(GL_TEXTURE0);
    glPixelTransferf(GL_BLUE_BIAS, -1);
    glUseProgram(0); //Use fixed pipeline
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0, getSize().x, 0, getSize().y, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    font.Render(output.c_str(), -1, point);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPixelTransferf(GL_BLUE_BIAS, 0);
}

void MainWindow::setupViewport() {
    GLfloat top = tan((FOV/180.0)*M_PI*0.5)*zNear;
    GLfloat aspect = (float)getSize().x/getSize().y;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(aspect*(-top), aspect*top, -top, top, zNear, zFar);
    glViewport(0, 0, getSize().x, getSize().y);
}

GLuint MainWindow::loadShaderSource(std::string filename, GLuint shaderType) {
    char *shaderSource;
    GLuint shader;

    shader = glCreateShader(shaderType); //Ask for a handle to a new shader
    shaderSource = CG::textFileRead(filename.c_str()); //Read the source code from file
    glShaderSource(shader, 1, (const char**)(&shaderSource),NULL); //Load the source code into the shader
    free(shaderSource); //Source code no longer needed

    return shader;
}

void MainWindow::onResize() {
    setupViewport();
}

void MainWindow::onCreate() {
    //Initialise settings
    glClearColor(0, 0, 0, 1);
    glEnable(GL_CULL_FACE);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    static const GLfloat lAmbient[4] = {0.3, 0.3, 0.3, 1.0};
    glLightfv(GL_LIGHT0, GL_AMBIENT_AND_DIFFUSE, lAmbient);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_NORMALIZE);
    setMouseCursorVisible(false);
    setupViewport();
}
void MainWindow::loadModels() {

    modelList.push_back(new Model("resources/models/spitfire/spitfire.obj"));
    instanceList.push_back(new ModelInstance((Model*)(modelList.at(0)), 280, 50, -480,modelShaderProg,-90.0,1,0,0,1.2,1.2,1.2));

    modelList.push_back(new Model("resources/models/mayan_pyramid/pyramid.obj"));
    instanceList.push_back(new ModelInstance((Model*)(modelList.at(1)), 370, 30, -330,modelShaderProg,0.0,0,0,0,0.30,0.30,0.30));

    //make sure it is at the end
    modelList.push_back(new Model("resources/models/mustang/Mustang.obj"));
    plane = new Plane(modelList.at(modelList.size()-1), 270, 50, -270, modelShaderProg, clock);

}

void MainWindow::renderInstanceList() {
    for(int i =0 ;i < instanceList.size() ;i++){
        instanceList.at(i)->render();
    }
}
void MainWindow::adjustPlanePitch(GLfloat angle) {
   plane->adjustPitch(angle);
}

void MainWindow::adjustPlaneRoll(GLfloat angle) {
    plane->adjustRoll(angle);
}

void MainWindow::adjustPlaneYaw(GLfloat angle) {
    plane->adjustYaw(angle);
}

void MainWindow::adjustPlaneSpeed(GLfloat speed) {
    plane->adjustSpeed(speed);
}

void MainWindow::keyHandler(sf::Keyboard::Key key){
    if(key == sf::Keyboard::E) {
        flying = !flying;
        return;
    }
    if(flying == true) {
        if(key == sf::Keyboard::W) {
            plane->adjustPitch(-2);
        } else if(key == sf::Keyboard::S) {
            plane->adjustPitch(2);
        } else if(key == sf::Keyboard::A) {
            plane->adjustYaw(2);
        } else if(key == sf::Keyboard::D) {
            plane->adjustYaw(-2);
        } else if(key == sf::Keyboard::Z) {
            plane->adjustSpeed(-2);
        } else if(key == sf::Keyboard::X) {
            plane->adjustSpeed(2);
        }
    } else {
        if(key == sf::Keyboard::W) {
            moveForward(20);
        } else if(key == sf::Keyboard::S) {
            moveBackward(20);
        } else if(key == sf::Keyboard::A) {
            moveLeft(20);
        } else if(key == sf::Keyboard::D) {
            moveRight(20);
        }
    }
}

void MainWindow::lockOnPlane() {
    GLfloat dx = -sin((plane->getYaw()/180)*M_PI)*10;
    GLfloat dy = -sin((plane->getPitch()/180)*M_PI)*10;
    GLfloat dz = -cos((plane->getYaw()/180)*M_PI)*10;
    camPos[0] = plane->getPosition().x+dx;
    camPos[1] = plane->getPosition().y+dy+5;
    camPos[2] = plane->getPosition().z+dz;
}

void MainWindow::planeCollision() {
    vec3 scale = terrain->getScale();
    GLuint terrainWidth = terrain->getWidth();
    GLuint terrainHeight = terrain->getHeight();
    GLfloat *heightmap = terrain->getHeightmap();
    vec3 planePos = plane->getPosition();
    GLfloat height = 0;
    if(planePos.x >= terrainWidth*scale.x || planePos.x < 0 || ((terrainHeight*scale.z)+planePos.z) >= terrainHeight*scale.z || (terrainHeight*scale.z)+planePos.z < 0) {
        if(planePos.y < 3) {
            planePos.y = 3;
            plane->setPosition(planePos);
        }
    } else if(planePos.y < (height = (heightmap[(int)(planePos.x/scale.x) + (int)((int)((terrainHeight + (planePos.z)/scale.z))*terrainWidth)]*scale.y))){
        planePos.y = height+2;
        plane->setPosition(planePos);
    } else if(planePos.y < 3) {
        planePos.y = 3;
        plane->setPosition(planePos);
    }
}
